Skip to content

DOOML: add compiler doing everything for A (well, we hope to add everything)#50

Merged
Kakadu merged 58 commits intoKakadu:masterfrom
georgiy-belyanin:dooml-basic
Mar 16, 2026
Merged

DOOML: add compiler doing everything for A (well, we hope to add everything)#50
Kakadu merged 58 commits intoKakadu:masterfrom
georgiy-belyanin:dooml-basic

Conversation

@georgiy-belyanin
Copy link
Contributor

@georgiy-belyanin georgiy-belyanin commented Jan 16, 2026

This PR introduces a complete DOOML complier implementation. It was made by Georgiy Belyanin (@georgiy-belyanin) and Ignat Sergeev (@IgnatSergeev).

What features were implemented?

  • Basic parsing.
  • CC/LL.
  • ANF.
  • Basic rv64 compilation.
  • Partial application.
  • Mark-n-copy GC.
  • Tuples.
  • LLVM.

georgiy-belyanin and others added 30 commits January 16, 2026 19:28
This is quasi-initial commit of the DOOML language compiler done in
terms of functional language compilers 2025 SPbU course.

Basically, it is OCaml-like language with (drastically) truncated list
of features. This patch introduces a basic AST and parser with
pretty-printing facilities. Only integers, units and tuples are
supported yet.

This will be implemented by Georgiy Belyanin and Ignatiy Sergeev.
This patch introduces a-normal-form (ANF) as a step in the compiler's
middle-end. ANF is responsible to make the function calls only contain
variables or immediate values as their arguments which makes
the code kind of resemble assembly in the sense that assembler
instructions usually work with registers/immediate values not with
compound statements.

Example:

```ocaml
(* Before ANF *)
let f =
  let q = f ((g + sup0) * (2 * i)) in
  q
;;

(* After ANF *)
let f =
  let sup2 = (*) 2 i in
  let sup5 = (+) g sup0 in
  let sup6 = (*) sup5 sup2 in
  let sup7 = (f) sup6 in
  let q = sup7 in
  q
;;
```
This patch fixes AST pretty-printing by properly using format boxes. It
is relatively hard to describe what has changed since the previous usage
was completely wrong. Let's provide a few examples instead to notice the
difference between the old formatting and the new one.

Old:

```
let a = 15 in let b = 4 in
let c = 8 in ...
```

New:

```
let a = 15 in
let b = 4 in
let c = 8 in
...
```

Old:

```
let smth = if long_cond then long_one else long_two in
```

New:

```
let smth = if long_cond then
    long_one
  else
    long_two
in
```
Previous implementation of the if-then-else ANF process was wrong. It
actually executed both then and else branches and then chosen one of the
results. This patch fixes it and now both branches are executed
exclusively.
This patch prevents the parser from handling the keywords as
identifiers.
This patch introduces plugs, tuples, and units into the parser.

In other words, the following code can now be parsed properly.

```ocaml
let () = _;;
let _ = _;;
let (a, b) = _;;
```
This patch fixes the if-then-else (ite) condition parsing. The problem
was that it was impossible to use ite inside ite conditions (for some
unknown reason). The problem could be fixed by removing a few
parser-combinator commits. Let's do it even though it would make errors
less useful.

Actually, the patch improves pretty-printing too.
This patch significantly improves the ANF stage. It does the following.

* It makes ANF accept the whole program as input.
* It improves pretty-printing (similarly to AST in the previous patch).
* It allows to ANF tuples as follows.

Before ANF.
```
let (a, b) = c;;
```

After ANF.
```
let a = nth c 0;;

let b = nth c 1;;
```

It also makes it internally use a state monad.
This patch introduces basic closure-conversion and lambda-lifting
allowing DOOML to handle closures. They are usually used together so
they are added in a single patch.

```ocaml
let f = fun a c ->
  let g = fun b -> a + b in
  g c
;;

(* CC turns it into... *)

let f = fun a c ->
  let g = (fun a b -> a + b) a in
  g c
;;

(* LL turns it into... *)

let g = (fun a b -> a + b) a;;

let f = fun a c ->
  g c
;;
```
This patch introduces basic DOOML compilation into RISC-V assembly,
namely rv64gc. The patch does not yet verify it. However, a few tests
are about to be added soon.

Additionally, there is C runtime. It must be compiled by cross-toolchain
and linked across the generated assembly in order for everything to work
properly.
separate
@georgiy-belyanin georgiy-belyanin force-pushed the dooml-basic branch 2 times, most recently from 8d3edd1 to f23b6c7 Compare January 26, 2026 11:59
Let's try to cleanup everything...
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zanuda-linter report

@@ -0,0 +1,431 @@
open Angstrom

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File 'DOOML/lib/fe.ml' doesn't have corresponding .mli interface

open DOOML
module Map = Base.Map.Poly

let failf fmt = Format.kasprintf failwith fmt

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

open DOOML
module Map = Base.Map.Poly

let failf fmt = Format.kasprintf failwith fmt

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

@@ -0,0 +1,427 @@
type immexpr =

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OCaml files should provide license information in second line (structure item)

cond_
in
return ret
| Fun _ -> failwith "should be CC/LL first"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

| Etc of string
[@@deriving variants]

let todo () = failwith "todo"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

open State

let spf = Format.asprintf
let failf fmt = Format.kasprintf failwith fmt

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

| ">=" -> [ slt rd rs1 rs2; xori rd rd 1 ]
| "=" -> [ sub (temp 0) rs1 rs2; seqz rd (temp 0) ]
| "<>" -> [ sub (temp 0) rs1 rs2; snez rd (temp 0) ]
| _ -> assert false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

in
let argc = List.length args in
(match arity with
| `Fun arity when arity == argc ->

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No description provided.

@@ -0,0 +1,19 @@
module M (S : sig

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OCaml files should provide license information in second line (structure item)

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zanuda-linter report

@@ -0,0 +1,431 @@
open Angstrom

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File 'DOOML/lib/fe.ml' doesn't have corresponding .mli interface

open DOOML
module Map = Base.Map.Poly

let failf fmt = Format.kasprintf failwith fmt

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

open DOOML
module Map = Base.Map.Poly

let failf fmt = Format.kasprintf failwith fmt

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

@@ -0,0 +1,427 @@
type immexpr =

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OCaml files should provide license information in second line (structure item)

cond_
in
return ret
| Fun _ -> failwith "should be CC/LL first"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

| Etc of string
[@@deriving variants]

let todo () = failwith "todo"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

open State

let spf = Format.asprintf
let failf fmt = Format.kasprintf failwith fmt

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

| ">=" -> [ slt rd rs1 rs2; xori rd rd 1 ]
| "=" -> [ sub (temp 0) rs1 rs2; seqz rd (temp 0) ]
| "<>" -> [ sub (temp 0) rs1 rs2; snez rd (temp 0) ]
| _ -> assert false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

in
let argc = List.length args in
(match arity with
| `Fun arity when arity == argc ->

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No description provided.

@@ -0,0 +1,19 @@
module M (S : sig

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OCaml files should provide license information in second line (structure item)

@github-actions
Copy link

Документация и тестовое покрытие (75.88%) должны скоро появиться.

https://kakadu.github.io/comp25/docs/DOOML

https://kakadu.github.io/comp25/cov/DOOML

2026-03-16 18:23

Email Commits Files Insertions Deletions Total Lines
ignat.sergeev@softcom.su 47 38 2627 1596 4223
belyaningeorge@ya.ru 11 41 3436 352 3788
----- ------- ----- ---------- --------- ---------

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zanuda-linter report

@@ -0,0 +1,120 @@
[@@@ocaml.text "/*"]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File 'DOOML/lib/llvm_wrapper.ml' doesn't have corresponding .mli interface

open DOOML
module Map = Base.Map.Poly

let failf fmt = Format.kasprintf failwith fmt

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

open DOOML
module Map = Base.Map.Poly

let failf fmt = Format.kasprintf failwith fmt

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

cond_
in
return ret
| Fun _ -> failwith "should be CC/LL first"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated


open (val Llvm_wrapper.make context builder the_module)

let failf fmt = Format.kasprintf failwith fmt

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

ctx'.lifts, ctx', ast
in
ctx := reset ctx';
ctx := extend pattern !ctx |> snd;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using mutable data structures for teaching purposes is usually discouraged. Replace Hashtables by standard tree-like maps or consider Hash-Array Mapped Tries (HAMT). Use mutable references and mutable structure fields only if it is really required. In all places where it is needed indeed, describe in a comment why it is needed there.

ctx'.lifts, ctx', ast
in
ctx := reset ctx';
ctx := extend pattern !ctx |> snd;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using mutable data structures for teaching purposes is usually discouraged. Replace Hashtables by standard tree-like maps or consider Hash-Array Mapped Tries (HAMT). Use mutable references and mutable structure fields only if it is really required. In all places where it is needed indeed, describe in a comment why it is needed there.

| Etc of string
[@@deriving variants]

let todo () = failwith "todo"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

open State

let spf = Format.asprintf
let failf fmt = Format.kasprintf failwith fmt

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

| ">=" -> [ slt rd rs1 rs2; xori rd rd 1 ]
| "=" -> [ sub (temp 0) rs1 rs2; seqz rd (temp 0) ]
| "<>" -> [ sub (temp 0) rs1 rs2; snez rd (temp 0) ]
| _ -> assert false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using failwith (or assert false) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

@github-actions
Copy link

Linter report from 2026-03-16 18:24, for mini language DOOML

File '_build/default/lib/llvm_wrapper.ml' doesn't have corresponding .mli interface

File "bin/llvm.ml", line 8, characters 33-41:
8 | let failf fmt = Format.kasprintf failwith fmt
                                     ^^^^^^^^
Alert zanuda-linter: Using `failwith` (or `assert false`) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated
File "bin/riscv.ml", line 8, characters 33-41:
8 | let failf fmt = Format.kasprintf failwith fmt
                                     ^^^^^^^^
Alert zanuda-linter: Using `failwith` (or `assert false`) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated
File "lib/anf.ml", line 214, characters 13-21:
214 |   | Fun _ -> failwith "should be CC/LL first"
                   ^^^^^^^^
Alert zanuda-linter: Using `failwith` (or `assert false`) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated
File "lib/codegen.ml", line 13, characters 33-41:
13 | let failf fmt = Format.kasprintf failwith fmt
                                      ^^^^^^^^
Alert zanuda-linter: Using `failwith` (or `assert false`) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated
File "lib/codegen.ml", line 42, characters 9-21:
42 |   | _ -> assert false
              ^^^^^^^^^^^^
Alert zanuda-linter: Using `failwith` (or `assert false`) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated
File "lib/codegen.ml", line 91, characters 47-59:
91 |   Map.merge_skewed ~combine:(fun ~key:_ _ _ -> assert false) rt binops
                                                    ^^^^^^^^^^^^
Alert zanuda-linter: Using `failwith` (or `assert false`) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated
File "lib/fe.ml", line 179, characters 9-21:
179 |   | _ -> assert false
               ^^^^^^^^^^^^
Alert zanuda-linter: Using `failwith` (or `assert false`) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated
File "lib/ll.ml", line 104, characters 7-10:
104 |       (ctx
             ^^^
Alert zanuda-linter: Using mutable data structures for teaching purposes is usually discouraged. Replace Hashtables by standard tree-like maps or consider Hash-Array Mapped Tries (HAMT). Use mutable `ref`erences and mutable structure fields only if it is really required. In all places where it is needed indeed, describe in a comment why it is needed there.
File "lib/ll.ml", line 106, characters 39-42:
106 |           | Ast.Rec -> extend pattern !ctx |> snd
                                             ^^^
Alert zanuda-linter: Using mutable data structures for teaching purposes is usually discouraged. Replace Hashtables by standard tree-like maps or consider Hash-Array Mapped Tries (HAMT). Use mutable `ref`erences and mutable structure fields only if it is really required. In all places where it is needed indeed, describe in a comment why it is needed there.
File "lib/ll.ml", line 107, characters 27-30:
107 |           | Ast.NonRec -> !ctx);
                                 ^^^
Alert zanuda-linter: Using mutable data structures for teaching purposes is usually discouraged. Replace Hashtables by standard tree-like maps or consider Hash-Array Mapped Tries (HAMT). Use mutable `ref`erences and mutable structure fields only if it is really required. In all places where it is needed indeed, describe in a comment why it is needed there.
File "lib/ll.ml", line 111, characters 36-39:
111 |           let body, ctx' = ll body !ctx in
                                          ^^^
Alert zanuda-linter: Using mutable data structures for teaching purposes is usually discouraged. Replace Hashtables by standard tree-like maps or consider Hash-Array Mapped Tries (HAMT). Use mutable `ref`erences and mutable structure fields only if it is really required. In all places where it is needed indeed, describe in a comment why it is needed there.
File "lib/ll.ml", line 114, characters 34-37:
114 |           let ast, ctx' = ll ast !ctx in
                                        ^^^
Alert zanuda-linter: Using mutable data structures for teaching purposes is usually discouraged. Replace Hashtables by standard tree-like maps or consider Hash-Array Mapped Tries (HAMT). Use mutable `ref`erences and mutable structure fields only if it is really required. In all places where it is needed indeed, describe in a comment why it is needed there.
File "lib/ll.ml", line 117, characters 6-9:
117 |       ctx := reset ctx';
            ^^^
Alert zanuda-linter: Using mutable data structures for teaching purposes is usually discouraged. Replace Hashtables by standard tree-like maps or consider Hash-Array Mapped Tries (HAMT). Use mutable `ref`erences and mutable structure fields only if it is really required. In all places where it is needed indeed, describe in a comment why it is needed there.
File "lib/ll.ml", line 118, characters 6-9:
118 |       ctx := extend pattern !ctx |> snd;
            ^^^
Alert zanuda-linter: Using mutable data structures for teaching purposes is usually discouraged. Replace Hashtables by standard tree-like maps or consider Hash-Array Mapped Tries (HAMT). Use mutable `ref`erences and mutable structure fields only if it is really required. In all places where it is needed indeed, describe in a comment why it is needed there.
File "lib/ll.ml", line 118, characters 29-32:
118 |       ctx := extend pattern !ctx |> snd;
                                   ^^^
Alert zanuda-linter: Using mutable data structures for teaching purposes is usually discouraged. Replace Hashtables by standard tree-like maps or consider Hash-Array Mapped Tries (HAMT). Use mutable `ref`erences and mutable structure fields only if it is really required. In all places where it is needed indeed, describe in a comment why it is needed there.
File "lib/riscv.ml", line 277, characters 14-22:
277 | let todo () = failwith "todo"
                    ^^^^^^^^
Alert zanuda-linter: Using `failwith` (or `assert false`) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated
File "lib/riscv.ml", line 424, characters 33-41:
424 | let failf fmt = Format.kasprintf failwith fmt
                                       ^^^^^^^^
Alert zanuda-linter: Using `failwith` (or `assert false`) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated
File "lib/riscv.ml", line 480, characters 15-27:
480 |         | _ -> assert false
                     ^^^^^^^^^^^^
Alert zanuda-linter: Using `failwith` (or `assert false`) usually is a clue that a corner case is not being handled properly. To report errors we recommend using error monad instead. In princliple, these construction are OK for temporary work-in-progress code, but in release they should be eliminated

@Kakadu Kakadu marked this pull request as ready for review March 16, 2026 17:32
@Kakadu
Copy link
Owner

Kakadu commented Mar 16, 2026

Давайте сделаем вид, что тут всё достаточно хорошо для ТПшек.

@Kakadu Kakadu merged commit fd1c917 into Kakadu:master Mar 16, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants